/*
  Copyright (C) 2001 Bertrik Sikken (bertrik@zonnet.nl)

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  as published by the Free Software Foundation; either version 2
  of the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

  $Id: niash_xfer.c,v 1.4 2006-02-04 10:28:50 ullsig-guest Exp $
*/

/*
        Provides a simple interface to read and write data from the scanner,
        without any knowledge whether it's a parallel or USB scanner
*/

#include <stdio.h>		/* printf */
#include <errno.h>		/* better error reports */
#include <string.h>		/* better error reports */

#include "niash_xfer.h"

#include "sane/sanei_usb.h"

/* list of supported models */
STATIC TScannerModel ScannerModels[] = {
	{"Hewlett-Packard", "ScanJet 3300C", 0x3F0, 0x205, eHp3300c}
	,
	{"Hewlett-Packard", "ScanJet 3400C", 0x3F0, 0x405, eHp3400c}
	,
	{"Hewlett-Packard", "ScanJet 4300C", 0x3F0, 0x305, eHp4300c}
	,
	{"Silitek Corp.", "HP ScanJet 4300c", 0x47b, 0x1002, eHp3400c}
	,
	{"Agfa", "Snapscan Touch", 0x6BD, 0x100, eAgfaTouch}
	,
	{"Trust", "Office Scanner USB 19200", 0x47b, 0x1000, eAgfaTouch}
	,
/* last entry all zeros */
	{0, 0, 0, 0, 0}
};

static TFnReportDevice *_pfnReportDevice;
static TScannerModel *_pModel;

/*
  MatchUsbDevice
  ==============
        Matches a given USB vendor and product id against a list of
        supported scanners.

  IN  iVendor   USB vendor ID
          iProduct  USB product ID
  OUT *ppModel  Pointer to TScannerModel structure

  Returns TRUE if a matching USB scanner was found
*/
STATIC SANE_Bool
MatchUsbDevice(int iVendor, int iProduct, TScannerModel ** ppModel)
{
	TScannerModel *pModels = ScannerModels;

	DBG(DBG_MSG, "Matching USB device 0x%04X-0x%04X ... ", iVendor,
	    iProduct);
	while (pModels->pszName != NULL) {
		if ((pModels->iVendor == iVendor)
		    && (pModels->iProduct == iProduct)) {
			DBG(DBG_MSG, "found %s %s\n", pModels->pszVendor,
			    pModels->pszName);
			*ppModel = pModels;
			return SANE_TRUE;
		}
		/* next model to match */
		pModels++;
	}
	DBG(DBG_MSG, "nothing found\n");
	return SANE_FALSE;
}

/************************************************************************
  Public functions for the SANE compilation
************************************************************************/


/* callback for sanei_usb_attach_matching_devices */
static SANE_Status
_AttachUsb(SANE_String_Const devname)
{
	DBG(DBG_MSG, "_AttachUsb: found %s\n", devname);

	_pfnReportDevice(_pModel, (const char *) devname);

	return SANE_STATUS_GOOD;
}


/*
  NiashXferInit
  ===============
        Initialises all registered data transfer modules, which causes
        them to report any devices found through the pfnReport callback.

  IN  pfnReport Function to call to report a transfer device
*/
static void
NiashXferInit(TFnReportDevice * pfnReport)
{
	TScannerModel *pModels = ScannerModels;

	sanei_usb_init();
	_pfnReportDevice = pfnReport;

	/* loop over all scanner models */
	while (pModels->pszName != NULL) {
		DBG(DBG_MSG, "Looking for %s...\n", pModels->pszName);
		_pModel = pModels;
		if (sanei_usb_find_devices((SANE_Int) pModels->iVendor,
					   (SANE_Int) pModels->iProduct,
					   _AttachUsb) != SANE_STATUS_GOOD) {

			DBG(DBG_ERR, "Error invoking sanei_usb_find_devices");
			break;
		}
		pModels++;
	}
}


static int
NiashXferOpen(const char *pszName, EScannerModel * peModel)
{
	SANE_Status status;
	SANE_Word vendor, product;
	int fd;
	TScannerModel *pModel = 0;

	DBG(DBG_MSG, "Trying to open %s...\n", pszName);

	status = sanei_usb_open(pszName, &fd);
	if (status != SANE_STATUS_GOOD) {
		return -1;
	}

	status = sanei_usb_get_vendor_product(fd, &vendor, &product);
	if (status == SANE_STATUS_GOOD) {
		MatchUsbDevice(vendor, product, &pModel);
		*peModel = pModel->eModel;
	}

	DBG(DBG_MSG, "handle = %d\n", (int) fd);
	return fd;
}


static void
NiashXferClose(int iHandle)
{
	/* close usb device */
	if (iHandle != -1) {
		sanei_usb_close(iHandle);
	}
}


static void
parusb_write_reg(int fd, unsigned char bReg, unsigned char bValue)
{
	sanei_usb_control_msg(fd,
			      USB_TYPE_VENDOR | USB_RECIP_DEVICE |
			      USB_DIR_OUT, 0x0C, bReg, 0, 1, &bValue);
}


static void
parusb_read_reg(int fd, unsigned char bReg, unsigned char *pbValue)
{
	sanei_usb_control_msg(fd,
			      USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
			      0x0C, bReg, 0, 1, pbValue);
}


static void
NiashWriteReg(int iHandle, unsigned char bReg, unsigned char bData)
{
	if (iHandle < 0) {
		DBG(DBG_MSG, "Invalid handle %d\n", iHandle);
		return;
	}

	parusb_write_reg(iHandle, SPP_CONTROL, 0x14);
	parusb_write_reg(iHandle, EPP_ADDR, bReg);
	parusb_write_reg(iHandle, SPP_CONTROL, 0x14);
	parusb_write_reg(iHandle, EPP_DATA_WRITE, bData);
	parusb_write_reg(iHandle, SPP_CONTROL, 0x14);
}


static void
NiashReadReg(int iHandle, unsigned char bReg, unsigned char *pbData)
{
	if (iHandle < 0) {
		return;
	}

	parusb_write_reg(iHandle, SPP_CONTROL, 0x14);
	parusb_write_reg(iHandle, EPP_ADDR, bReg);
	parusb_write_reg(iHandle, SPP_CONTROL, 0x34);
	parusb_read_reg(iHandle, EPP_DATA_READ, pbData);
	parusb_write_reg(iHandle, SPP_CONTROL, 0x14);
}


static void
NiashWriteBulk(int iHandle, unsigned char *pabBuf, int iSize)
{
	/*  byte  abSetup[8] = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
	   HP3400 probably needs 0x01, 0x01 */
	SANE_Byte abSetup[8] =
		{ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
	size_t size;

	if (iHandle < 0) {
		return;
	}

	/* select scanner register 0x24 */
	parusb_write_reg(iHandle, SPP_CONTROL, 0x14);
	parusb_write_reg(iHandle, EPP_ADDR, 0x24);
	parusb_write_reg(iHandle, SPP_CONTROL, 0x14);

	/* tell scanner that a bulk transfer follows */
	abSetup[4] = (iSize) & 0xFF;
	abSetup[5] = (iSize >> 8) & 0xFF;
	sanei_usb_control_msg(iHandle,
			      USB_TYPE_VENDOR | USB_RECIP_DEVICE |
			      USB_DIR_OUT, 0x04, USB_SETUP, 0, 8, abSetup);

	/* do the bulk write */
	size = iSize;
	if (sanei_usb_write_bulk(iHandle, pabBuf, &size) != SANE_STATUS_GOOD) {
		DBG(DBG_ERR, "ERROR: Bulk write failed\n");
	}
}


static void
NiashReadBulk(int iHandle, unsigned char *pabBuf, int iSize)
{
	SANE_Byte abSetup[8] =
		{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
	size_t size;

	if (iHandle < 0) {
		return;
	}

	/* select scanner register 0x24 */
	parusb_write_reg(iHandle, SPP_CONTROL, 0x14);
	parusb_write_reg(iHandle, EPP_ADDR, 0x24);
	parusb_write_reg(iHandle, SPP_CONTROL, 0x14);

	/* tell scanner that a bulk transfer follows */
	abSetup[4] = (iSize) & 0xFF;
	abSetup[5] = (iSize >> 8) & 0xFF;
	sanei_usb_control_msg(iHandle,
			      USB_TYPE_VENDOR | USB_RECIP_DEVICE |
			      USB_DIR_OUT, 0x04, USB_SETUP, 0, 8, abSetup);

	/* do the bulk read */
	size = iSize;
	if (sanei_usb_read_bulk(iHandle, pabBuf, &size) != SANE_STATUS_GOOD) {
		DBG(DBG_ERR, "ERROR: Bulk read failed\n");
	}
}


static void
NiashWakeup(int iHandle)
{
	unsigned char abMagic[] =
		{ 0xA0, 0xA8, 0x50, 0x58, 0x90, 0x98, 0xC0, 0xC8,
		0x90, 0x98, 0xE0, 0xE8
	};
	int i;

	if (iHandle < 0) {
		return;
	}

	/* write magic startup sequence */
	parusb_write_reg(iHandle, SPP_CONTROL, 0x14);
	for (i = 0; i < (int) sizeof(abMagic); i++) {
		parusb_write_reg(iHandle, SPP_DATA, abMagic[i]);
	}

	/* write 0x04 to scanner register 0x00 the hard way */
	parusb_write_reg(iHandle, SPP_DATA, 0x00);
	parusb_write_reg(iHandle, SPP_CONTROL, 0x14);
	parusb_write_reg(iHandle, SPP_CONTROL, 0x15);
	parusb_write_reg(iHandle, SPP_CONTROL, 0x1D);
	parusb_write_reg(iHandle, SPP_CONTROL, 0x15);
	parusb_write_reg(iHandle, SPP_CONTROL, 0x14);

	parusb_write_reg(iHandle, SPP_DATA, 0x04);
	parusb_write_reg(iHandle, SPP_CONTROL, 0x14);
	parusb_write_reg(iHandle, SPP_CONTROL, 0x15);
	parusb_write_reg(iHandle, SPP_CONTROL, 0x17);
	parusb_write_reg(iHandle, SPP_CONTROL, 0x15);
	parusb_write_reg(iHandle, SPP_CONTROL, 0x14);
}
